/*
 * JBox2D - A Java Port of Erin Catto's Box2D
 * 
 * JBox2D homepage: http://jbox2d.sourceforge.net/ 
 * Box2D homepage: http://www.box2d.org
 * 
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

package org.jbox2d.dynamics.joints;

import org.jbox2d.common.Mat22;
import org.jbox2d.common.Settings;
import org.jbox2d.common.Vec2;
import org.jbox2d.common.XForm;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.TimeStep;


//Updated to rev 56->130 of b2MouseJoint.cpp/.h

//p = attached point, m = mouse point
//C = p - m
//Cdot = v
//   = v + cross(w, r)
//J = [I r_skew]
//Identity used:
//w k % (rx i + ry j) = w * (-ry i + rx j)

public class MouseJoint extends Joint {

    public Vec2 m_localAnchor;

    public Vec2 m_target;

    public Vec2 m_force;

    public Mat22 m_mass; // effective mass for point-to-point constraint.

    public Vec2 m_C; // position error

    public float m_maxForce;

    public float m_beta; // bias factor

    public float m_gamma; // softness

    public MouseJoint(MouseJointDef def) {
        super(def);

        m_force = new Vec2();
        m_target = new Vec2();
        m_C = new Vec2();
        m_mass = new Mat22();
        m_target = def.target;
        m_localAnchor = XForm.mulT(m_body2.m_xf, m_target);

        m_maxForce = def.maxForce;

        float mass = m_body2.m_mass;

        // Frequency
        float omega = 2.0f * Settings.pi * def.frequencyHz;

        // Damping coefficient
        float d = 2.0f * mass * def.dampingRatio * omega;

        // Spring stiffness
        float k = mass * omega * omega;

        // magic formulas
        m_gamma = 1.0f / (d + def.timeStep * k);
        m_beta = def.timeStep * k / (d + def.timeStep * k);

    }

    /** Use this to update the target point. */
    public void setTarget(Vec2 target) {
        if (m_body2.isSleeping()) m_body2.wakeUp();
        m_target = target;
    }

    @Override
    public Vec2 getAnchor1() {
        return m_target;
    }

    @Override
    public Vec2 getAnchor2() {
    	return m_body2.getWorldPoint(m_localAnchor);
    }

    @Override
    public void initVelocityConstraints(TimeStep step) {
        Body b = m_body2;

     // Compute the effective mass matrix.
    	Vec2 r = Mat22.mul(b.m_xf.R, m_localAnchor.sub(b.getLocalCenter()));

        // K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2)
        // * invI2 * skew(r2)]
        // = [1/m1+1/m2 0 ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 *
        // [r1.y*r1.y -r1.x*r1.y]
        // [ 0 1/m1+1/m2] [-r1.x*r1.y r1.x*r1.x] [-r1.x*r1.y r1.x*r1.x]
        float invMass = b.m_invMass;
        float invI = b.m_invI;

        Mat22 K1 = new Mat22(invMass, 0.0f, 0.0f, invMass);

        Mat22 K2 = new Mat22(invI * r.y * r.y, -invI * r.x * r.y, -invI * r.x
                * r.y, invI * r.x * r.x);

        Mat22 K = K1.add(K2);
        K.col1.x += m_gamma;
        K.col2.y += m_gamma;

        m_mass.set(K);
        m_mass = m_mass.invert();

        m_C.set(b.m_sweep.c.x + r.x - m_target.x, b.m_sweep.c.y + r.y - m_target.y);

        // Cheat with some damping
        b.m_angularVelocity *= 0.98f;

        // Warm starting.
    	float Px = step.dt * m_force.x;
    	float Py = step.dt * m_force.y;
    	b.m_linearVelocity.x += invMass * Px;
    	b.m_linearVelocity.y += invMass * Py;
    	b.m_angularVelocity += invI * (r.x*Py-r.y*Px);
    }

    @Override
    public boolean solvePositionConstraints() {
        return true;
    }

    @Override
    public void solveVelocityConstraints(TimeStep step) {
    	Body b = m_body2;

    	Vec2 r = Mat22.mul(b.m_xf.R, m_localAnchor.sub(b.getLocalCenter()));

    	// Cdot = v + cross(w, r)
    	Vec2 Cdot = b.m_linearVelocity.add(Vec2.cross(b.m_angularVelocity, r));

    	//Vec2 force = -step.inv_dt * Mat22.mul(m_mass, Cdot + (m_beta * step.inv_dt) * m_C + m_gamma * step.dt * m_force);
    	Vec2 force = new Vec2( Cdot.x + (m_beta*step.inv_dt)*m_C.x + m_gamma * step.dt * m_force.x, 
    						   Cdot.y + (m_beta*step.inv_dt)*m_C.y + m_gamma * step.dt * m_force.y );
    	force = Mat22.mul(m_mass,force);
    	force.mulLocal(-step.inv_dt);

    	Vec2 oldForce = m_force.clone();
    	m_force.addLocal(force);
    	float forceMagnitude = m_force.length();
    	if (forceMagnitude > m_maxForce) {
    		m_force.mulLocal(m_maxForce / forceMagnitude);
    	}
    	force.set(m_force.x - oldForce.x, m_force.y - oldForce.y);

    	Vec2 P = new Vec2(step.dt * force.x, step.dt * force.y);
    	b.m_linearVelocity.addLocal(P.mul(b.m_invMass));
    	b.m_angularVelocity += b.m_invI * Vec2.cross(r, P);
    }

    @Override
    public Vec2 getReactionForce() {
        return m_force;
    }

	@Override
	public float getReactionTorque() {
		return 0.0f;
	}
}
